package cn.yh.register.hosp.service.impl;
import java.math.BigDecimal;
import java.util.Date;

import cn.yh.register.client.cmn.CmnFeignClient;
import cn.yh.register.common.exception.RegisterException;
import cn.yh.register.common.result.ResultCodeEnum;
import cn.yh.register.hosp.repository.ScheduleRepository;
import cn.yh.register.hosp.service.DepartmentService;
import cn.yh.register.hosp.service.HospitalService;
import cn.yh.register.hosp.service.ScheduleService;
import cn.yh.register.model.hosp.BookingRule;
import cn.yh.register.model.hosp.Department;
import cn.yh.register.model.hosp.Hospital;
import cn.yh.register.model.hosp.Schedule;
import cn.yh.register.vo.hosp.BookingScheduleRuleVo;
import cn.yh.register.vo.hosp.ScheduleOrderVo;
import cn.yh.register.vo.hosp.ScheduleQueryVo;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    ScheduleRepository scheduleRepository;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private HospitalService hospitalService;

    @Autowired
    private DepartmentService departmentService;

//    @Autowired
//    private CmnFeignClient cmnFeignClient;

    /**
     * 上传排班接口
     * @param objectMap 参数信息
     */
    @Override
    public void saveSchedule(Map<String, Object> objectMap) {
        //转成对象保存
        Schedule schedule = JSONObject.parseObject(JSONObject.toJSONString(objectMap), Schedule.class);
        //查询是否存在
        Schedule sche = scheduleRepository.findByHoscodeAndHosScheduleId(schedule.getHoscode(), schedule.getHosScheduleId());
        //判断
        if (sche == null){
            //不存在  执行插入
            schedule.setCreateTime(new Date());
            schedule.setIsDeleted(0);
        }else{
            //存在  执行更新
            schedule.setId(sche.getId());
            schedule.setIsDeleted(sche.getIsDeleted());
            schedule.setCreateTime(sche.getCreateTime());
        }
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
    }

    /**
     * 分页查询排班列表
     *
     * @param queryvo 查询条件
     * @param page    页码
     * @param limit   每页条数
     * @return 分页的信息
     */
    @Override
    public Page<Schedule> getScheduleList(ScheduleQueryVo queryvo, Integer page, Integer limit) {
        //再理解一下子？？？？
        Schedule schedule = new Schedule();
        /**
         * 把条件设置进去，条件封装存在queryvo中，
         * 从queryvo中取出再放到schedule里面，用来匹配
         * 两者的属性名字是完全相同的，可以进行拷贝一下
         */
        BeanUtils.copyProperties(queryvo,schedule);
        //Matcher匹配器
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)
                .withIgnoreCase(true);
        //Example  模糊查询提供一个匹配器matcher
        Example<Schedule> example = Example.of(schedule, matcher);
        //PageRequest
        PageRequest pageRequest = PageRequest.of(page-1,limit, Sort.by(Sort.Direction.DESC,"createTime"));
        return scheduleRepository.findAll(example, pageRequest);
    }

    /**
     * 删除医院排班信息
     *
     * @param hoscode       医院编号
     * @param hosScheduleId 排班医院的唯一主键
     */
    @Override
    public void removeSchedule(String hoscode, String hosScheduleId) {
        //先查
        Schedule schedule = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
        if (schedule != null){
            //执行删除操作
            scheduleRepository.delete(schedule);
        }
    }

    /**
     * 分页显示医院排班信息
     *
     * @param hoscode 医院编号
     * @param depcode 科室编号
     * @param page    当前页
     * @param size    每页大小
     * @return 放在map集合中的排班信息
     */
    @Override
    public Map<String, Object> getHospSchedule(String hoscode, String depcode, Integer page, Integer size) {
        //定义查询条件
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
        //定义聚合操作
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate") //按照工作日期进行分组  因为当天会有很多出诊医生，在当天出诊的为一组，把这些workDate相同的为一组展示出来
                .first("workDate").as("workDate") //给分组的字段起别名与实体类对应  便于封装
                .count().as("docCount")//当天的排班数量就是出诊医生数量
                .sum("reservedNumber").as("reservedNumber") //统计当天的可预约数
                .sum("availableNumber").as("availableNumber") ,//统计剩余预约数
                Aggregation.sort(Sort.Direction.DESC, "workDate"),//按照排班日期进行排序
                //分组之后进行分页，这里的分页是对医生排班列表进行分页
                Aggregation.skip((page-1)*size),  //分页
                Aggregation.limit(size) //每页显示几条
        );

        //执行聚合操作  聚合操作只取了一部分字段  进一步封装成BookingScheduleRuleVo对象
        AggregationResults<BookingScheduleRuleVo> aggregationResults =
                mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        //取出数据
        List<BookingScheduleRuleVo> mappedResults = aggregationResults.getMappedResults();

        //根据排班日期  进行分组  便于分页展示
        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate")
        );
        //取结果  得到按照日期分组总共有多少组
        int total = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class).getMappedResults().size();
        //遍历列表数据  进行周几转换 并设置到vo对象中
        for (BookingScheduleRuleVo rule : mappedResults) {
            //取出日期
            Date workDate = rule.getWorkDate();
            //转换成周几
            String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));
            //放到vo对象中
            rule.setDayOfWeek(dayOfWeek);
        }
        //获取医院名称
        String hosname = hospitalService.getHosp(hoscode).getHosname();
        //其他基础数据装到map集合中
        Map<String ,String> baseMap = new HashMap<>();
        baseMap.put("hosname",hosname);
        //组装数据到map中
        Map<String,Object> result = new HashMap<>();
        result.put("mappedResults",mappedResults);
        result.put("baseMap", baseMap);
        result.put("total", total);
        return result;
    }

    /**
     * 获取排班详情信息
     *
     * @param hoscode  医院编号
     * @param depcode  科室编号
     * @param workDate 排班日期
     * @return 排班详情信息
     */
    @Override
    public List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate) {
        return scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode, depcode, new DateTime(workDate).toDate());
    }

    /**
     * 分页获取可预约的排班数据
     *
     * @param page    当前页
     * @param size    页大小
     * @param hoscode 医院编号
     * @param depcode 科室编号
     * @return 预约信息
     */
    @Override
    public Map<String, Object> getBookingScheduleRule(Integer page, Integer size, String hoscode, String depcode) {
        //拿到医院的信息
        Hospital hospital = hospitalService.getHosp(hoscode);
        if (hospital == null) {
            throw new RegisterException(ResultCodeEnum.PARAM_ERROR);
        }
        //获取医院的预约规则
        BookingRule bookingRule = hospital.getBookingRule();
        //根据预约规则分页展示可预约的日期列表
        IPage<Date> dataIPage = this.getPageList(page, size, bookingRule);
        //当日的日期列表对象
        List<Date> dateList = dataIPage.getRecords();
        //查询可预约的排班数据
        //定义匹配条件
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(dateList);
        //定义聚合操作
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),  //定义匹配条件
                Aggregation.group("workDate")
                .first("workDate").as("workDate")
                .count().as("docCount") //排班医生的数量
                .sum("reservedNumber").as("reservedNumber") //分组后当天可预约的号源
                .sum("availableNumber").as("availableNumber") //当天剩余的预约号源
        );
        //执行聚合操作
        AggregationResults<BookingScheduleRuleVo> aggResults = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        //获取执行后的数据
        List<BookingScheduleRuleVo> queryVoList = aggResults.getMappedResults();
        //这里的queryVoList获取的日期集合 为了后续操作 转成map集合  key是日期  value是scheduleQueryVo对象
        Map<Date, BookingScheduleRuleVo> ruleVoMap = queryVoList.stream().
                collect(Collectors.toMap(BookingScheduleRuleVo::getWorkDate, BookingScheduleRuleVo -> BookingScheduleRuleVo));
        // 可预约的排班数据列表
        List<BookingScheduleRuleVo> voList = new ArrayList<>();
       //因为取到的日期并不一定在map集合中有对应的数据，有可能当天没有排班信息 所以遍历日期集合  取对应的数据
        for (int i = 0; i < dateList.size(); i++) {
            // 取key  找数据
            Date date = dateList.get(i);  //key
            BookingScheduleRuleVo bookingScheduleRuleVo = ruleVoMap.get(date);  //value  vo对象

            if (bookingScheduleRuleVo == null ){
                //这一天没有排班数据
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                bookingScheduleRuleVo.setAvailableNumber(-1);
                bookingScheduleRuleVo.setReservedNumber(0);
                bookingScheduleRuleVo.setStatus(0);  //0：正常  1：即将放号  -1：停止挂号
                bookingScheduleRuleVo.setDocCount(0); //没有医生
            } else {
                // 有排班数据，不同情况设置不同状态
                // 最后一页的最后一条数据为即将放号
                if (page == dataIPage.getPages() && i == dateList.size()-1) {
                    bookingScheduleRuleVo.setStatus(1); // 1：即将放号
                } else {
                    bookingScheduleRuleVo.setStatus(0); // 0：正常
                }
                // 如果当天过了停号时间，则不能预约
                if (page == 1 && i == 0) { // 第一天的第一条数据
                    // 停挂时间
                    DateTime stopTime = this.getDateTime(bookingRule.getStopTime(), new Date());
                    if (stopTime.isBeforeNow()) { // 停号时间在当前时间之前，说明当前已过停号时间
                        bookingScheduleRuleVo.setStatus(-1); // -1：当天已停止挂号
                    }
                }
            }
            //无论哪种情况 都要设置workDate
            bookingScheduleRuleVo.setWorkDate(date);
            bookingScheduleRuleVo.setWorkDateMd(date);
            bookingScheduleRuleVo.setDayOfWeek(this.getDayOfWeek(new DateTime(date)));
            voList.add(bookingScheduleRuleVo);
        }
        //组装其他基础数据
        Map<String ,String> baseMap = new HashMap<>();
        //医院名称
        baseMap.put("hosname", hospitalService.getHosp(hoscode).getHosname());
        //科室
        Department department =  departmentService.getDepartment(hoscode,depcode);
       //大科室
        baseMap.put("bigname", department.getBigname());
        //科室名称
        baseMap.put("depname",department.getDepname());
        // 日期 年月日
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        // 放号时间
        baseMap.put("releaseTime",bookingRule.getReleaseTime());
        //停号时间
        baseMap.put("stopTime",bookingRule.getStopTime());
        //组装到map中返回
        Map<String,Object> result = new HashMap<>();
        result.put("baseMap",baseMap);
        //可预约的排班数据
        result.put("bookingScheduleList",voList);
        //日期列表的总记录数
        result.put("total",dataIPage.getTotal());
        return result;
    }

    /**
     * 根据id获取排班信息
     *
     * @param scheduleId 排班id
     * @return 排班信息
     */
    @Override
    public Schedule getSchedule(String scheduleId) {
        Optional<Schedule> optional = scheduleRepository.findById(scheduleId);
        if (optional.isPresent()){
            Schedule schedule = optional.get();
            this.packSchedule(schedule);
            return schedule;
        }
        return null;
    }

    /**
     * 获取预约下达所需要的排班数据
     * @param scheduleId 排班id
     * @return 预约下单需要的排班数据
     */
    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        Schedule schedule = this.getSchedule(scheduleId);
        if (schedule == null){
            throw new RegisterException(ResultCodeEnum.PARAM_ERROR);
        }
        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
        //医院编号
        String hoscode = schedule.getHoscode();
        scheduleOrderVo.setHoscode(hoscode);
        //医院名称
        Hospital hospital = hospitalService.getHosp(hoscode);
        String hosname = hospital.getHosname();
        scheduleOrderVo.setHosname(hosname);
        //科室编号
        String depcode = schedule.getDepcode();
        scheduleOrderVo.setDepcode(depcode);
        //科室名称
        Department department = departmentService.getDepartment(schedule.getHoscode(), schedule.getDepcode());
        String depname = department.getDepname();
        scheduleOrderVo.setDepname(depname);
        //医院排班id
        scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
        //医生职称
        scheduleOrderVo.setTitle(schedule.getTitle());
        //安排日期
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());
        //可预约数
        scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
        //安排时间
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());
        //医事服务费
        scheduleOrderVo.setAmount(schedule.getAmount());
        //预约规则
        BookingRule bookingRule = hospital.getBookingRule();
        //退号日期
        Date quitDate = new DateTime(schedule.getWorkDate()).plusDays(bookingRule.getQuitDay()).toDate();
        //退号时间
        DateTime quitTime = this.getDateTime(bookingRule.getQuitTime(), quitDate);
        scheduleOrderVo.setQuitTime(quitTime.toDate());
        //预约开始时间
        DateTime startTime = this.getDateTime(bookingRule.getReleaseTime(), new Date());
        scheduleOrderVo.setStartTime(startTime.toDate());
        //预约结束时间
        DateTime endTime = this.getDateTime(bookingRule.getStopTime(), new DateTime().plusDays(bookingRule.getCycle()).toDate());
        scheduleOrderVo.setEndTime(endTime.toDate());
        //当天的停挂时间
        DateTime stopTime = this.getDateTime(bookingRule.getStopTime(), new Date());
        scheduleOrderVo.setStopTime(stopTime.toDate());
        return scheduleOrderVo;
    }

    /**
     * 更新排班信息
     *
     * @param schedule 排班实体
     */
    @Override
    public void update(Schedule schedule) {
        scheduleRepository.save(schedule);
    }

    /**
     * 根据科室排班主键 查排班信息
     *
     * @param hosScheduleId 排班主键
     * @return 科室排班信息
     */
    @Override
    public Schedule getByHosScheduleId(String hosScheduleId) {
        return scheduleRepository.findByHosScheduleId(hosScheduleId);
    }

    /**
     * 处理排班信息
     * 因为这些个字段不是在数据字典里面的存的，所以不需要调用数据字典的getName方法。
     * @param schedule 排班对象
     */
    private void packSchedule(Schedule schedule){
        //hosname
        String hosname = hospitalService.getHosp(schedule.getHoscode()).getHosname();
        //depname
        String depname = departmentService.getDepartment(schedule.getHoscode(), schedule.getDepcode()).getDepname();
        //dayOfWeek
        String dayOfWeek = this.getDayOfWeek(new DateTime(schedule.getWorkDate()));
        //组装到param中
        schedule.getParam().put("hosname",hosname);
        schedule.getParam().put("depname", depname);
        schedule.getParam().put("dayOfWeek",dayOfWeek);
    }

    /**
     * 根据预约规则分页计算可预约的日期列表
     * @param current 当前页
     * @param limit 每页大小
     * @param bookingRule 预约规则
     * @return 分页后的预约日期列表信息
     */
    private IPage<Date> getPageList(Integer current,Integer limit,BookingRule bookingRule){
        //获取到预约周期  假设10 也就是放10天的号
        Integer cycle = bookingRule.getCycle();
        //放号时间  数据库中存的是时间字符串 8:30
        String releaseTimeStr = bookingRule.getReleaseTime();
        //转成日期类型
        DateTime releaseTime  = this.getDateTime(releaseTimeStr, new Date());
        //判断当前时间与放号时间的关系 超过放号时间 ,预约周期cycle+1
        if (releaseTime.isBeforeNow()){
            cycle += 1;
        }
        //装日期列表的集合
        List<Date> dateList = new ArrayList<>();
        //计算可预约的日期列表  必须在医院的可预约周期内
        for (int i = 0; i < cycle; i++) {
            String timeString = new DateTime().plusDays(i).toString("yyyy-MM-dd");
            //放到list集合中
            dateList.add(new DateTime(timeString).toDate());
        }
        //对获取到的日期列表做分页
        int start = (current - 1) * limit;
        //终止页
        int end = start + limit;
        if ( end >= dateList.size()){
            end = dateList.size();
        }
        //当前页的日期列表数据 遍历放到list集合中
        List<Date> curDateList = new ArrayList<>();
        for (int i = start; i < end; i++) {
            //把dateList中的数据取出来 放到当前页的集合中
            curDateList.add(dateList.get(i));
        }
        //组装数据返回
        IPage<Date> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current,7,dateList.size());
        page.setRecords(curDateList);
        return page;
    }

    /**
     * 把时间字符串转为dataTime类型
     * @param dateStr 时间字符串（格式：HH:mm）
     * @param date 日期（yyyy-MM-dd）
     * @return  （格式yyyy-MM-dd HH:mm）
     */
    private DateTime getDateTime(String dateStr,Date date){
        String dateTimeString = new DateTime(date).toString("yyyy-MM-dd") + " "+ dateStr;
        return DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(dateTimeString);
    }


    /**
     * 根据日期获取周几
     * @param dateTime 日期
     * @return 周几
     */
    private String getDayOfWeek(DateTime dateTime) {
        String dayOfWeek = "";
        switch (dateTime.getDayOfWeek()) {
            case DateTimeConstants.SUNDAY:
                dayOfWeek = "周日";
                break;
            case DateTimeConstants.MONDAY:
                dayOfWeek = "周一";
                break;
            case DateTimeConstants.TUESDAY:
                dayOfWeek = "周二";
                break;
            case DateTimeConstants.WEDNESDAY:
                dayOfWeek = "周三";
                break;
            case DateTimeConstants.THURSDAY:
                dayOfWeek = "周四";
                break;
            case DateTimeConstants.FRIDAY:
                dayOfWeek = "周五";
                break;
            case DateTimeConstants.SATURDAY:
                dayOfWeek = "周六";
            default:
                break;
        }
        return dayOfWeek;
    }
}

